package org.angmarch.views;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ElementScatter;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.dialog.BaseDialog;
import ohos.agp.window.dialog.PopupDialog;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;


/*
 * Copyright (C) 2015 Angelo Marchesin.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class NiceSpinner extends DirectionalLayout implements Component.TouchEventListener {

    private static final int VERTICAL_OFFSET = 1;

    private int selectedIndex;
    private Element arrowDrawable;
    private PopupDialog popupDialog;
    private NiceSpinnerBaseAdapter adapter;

    private ListContainer.ItemClickedListener onItemClickListener;
    private ListContainer.ItemSelectedListener onItemSelectedListener;
    private OnSpinnerItemSelectedListener onSpinnerItemSelectedListener;

    private boolean isArrowHidden;
    private Color textColor = Color.BLACK;
    private int backgroundSelector;
    private int arrowDrawableTint;
    private int displayHeight;
    private int parentVerticalOffset;
    private int dropDownListPaddingBottom;
    private int arrowDrawableResId;
    private SpinnerTextFormatter spinnerTextFormatter = new SimpleSpinnerTextFormatter();
    private SpinnerTextFormatter selectedTextFormatter = new SimpleSpinnerTextFormatter();
    private PopUpTextAlignment horizontalAlignment = PopUpTextAlignment.CENTER;
    private Context context;
    private ListContainer listContainer;
    private Text text;
    private Image image;
    private AnimatorProperty imageAnimatorProperty;

    public NiceSpinner(Context context) {
        super(context);
        init(context);
    }

    public NiceSpinner(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context);
        handleAttribute(attrs);
    }

    public NiceSpinner(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(context);
        handleAttribute(attrs);
    }

    private void init(Context context) {
        this.context = context;
        Component component = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_layout_spinner, this, true);
        text = (Text) component.findComponentById(ResourceTable.Id_text);
        image = (Image) component.findComponentById(ResourceTable.Id_image);
        imageAnimatorProperty = image.createAnimatorProperty();
        int defaultPadding = 0;
        try {
            defaultPadding = (int) getResourceManager().getElement(ResourceTable.Float_one_and_a_half_grid_unit).getFloat();
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        int leftPadding = 0;
        try {
            leftPadding = (int) getResourceManager().getElement(ResourceTable.Float_three_grid_unit).getFloat();
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        text.setTextAlignment(TextAlignment.VERTICAL_CENTER | TextAlignment.START);
        text.setPadding(leftPadding, defaultPadding, 0, defaultPadding);
        image.setMarginRight(defaultPadding);
        setClickable(true);
        setTouchEventListener(this);
        measureDisplayHeight();
    }

    private void handleAttribute(AttrSet attrSet) {
        Optional<Attr> textSizeOptional = attrSet.getAttr("textSize");
        if (textSizeOptional.isPresent()) {
            int textSize = textSizeOptional.get().getDimensionValue();
            text.setTextSize(textSize, Text.TextSizeType.PX);
        }
        Optional<Attr> textColorOptional = attrSet.getAttr("textTint");
        if (textColorOptional.isPresent()) {
            textColor = textColorOptional.get().getColorValue();
            text.setTextColor(textColor);
        }
        Optional<Attr> hideArrow = attrSet.getAttr("hideArrow");
        hideArrow.ifPresent(attr -> {
            isArrowHidden = attr.getBoolValue();
            if (isArrowHidden) {
                image.setVisibility(INVISIBLE);
            }
        });

        Optional<Attr> arrowOptional = attrSet.getAttr("arrowDrawable");
        if (arrowOptional.isPresent()) {
            arrowDrawable = arrowOptional.get().getElement();
            setArrowDrawableOrHide(arrowDrawable);
            String value = arrowOptional.get().getStringValue();
            if (value.contains("$media:")) {
                String substring = value.substring(value.indexOf(":") + 1, value.length());
                try {
                    arrowDrawableResId = Integer.parseInt(substring);
                } catch (NumberFormatException e) {
                    arrowDrawableResId = ResourceTable.Media_ic_arrow_drop_down_black_24dp;
                    e.printStackTrace();
                }
            }
        } else {
            arrowDrawableResId = ResourceTable.Media_ic_arrow_drop_down_black_24dp;
            setArrowDrawable(ResourceTable.Media_ic_arrow_drop_down_black_24dp);
        }

        Optional<Attr> paddingOptional = attrSet.getAttr("dropDownListPaddingBottom");
        paddingOptional.ifPresent(attr -> dropDownListPaddingBottom = attr.getDimensionValue());

        Optional<Attr> selectorOption = attrSet.getAttr("backgroundSelector");
        if (selectorOption.isPresent()) {
            Element backgroundElement = selectorOption.get().getElement();
            setBackground(backgroundElement);
            String value = selectorOption.get().getStringValue();
            if (value.contains("$graphic:")) {
                String substring = value.substring(value.indexOf(":") + 1, value.length());
                try {
                    backgroundSelector = Integer.parseInt(substring);
                } catch (NumberFormatException e) {
                    backgroundSelector = ResourceTable.Graphic_selector;
                    e.printStackTrace();
                }
            }
        } else {
            ElementScatter elementScatter = ElementScatter.getInstance(context);
            Element backgroundElement = elementScatter.parse(ResourceTable.Graphic_selector);
            setBackground(backgroundElement);
            backgroundSelector = ResourceTable.Graphic_selector;
        }
    }

    private void measureDisplayHeight() {
        displayHeight = AttrHelper.vp2px(getResourceManager().getDeviceCapability().height, context);
    }

    private int getParentVerticalOffset() {
        if (parentVerticalOffset > 0) {
            return parentVerticalOffset;
        }
        int[] locationOnScreen = getLocationOnScreen();
        return parentVerticalOffset = locationOnScreen[VERTICAL_OFFSET];
    }

    private void setArrowDrawableOrHide(Element drawable) {
        if (!isArrowHidden) {
            if (drawable != null) {
                image.setImageElement(drawable);
            } else {
                image.setPixelMap(arrowDrawableResId);
            }
            image.setVisibility(Component.VISIBLE);
        } else {
            image.setVisibility(Component.INVISIBLE);
        }
    }

    public Object getItemAtPosition(int position) {
        return adapter.getItemInDataset(position);
    }

    public Object getSelectedItem() {
        return adapter.getItemInDataset(selectedIndex);
    }

    public int getSelectedIndex() {
        return selectedIndex;
    }

    public void setArrowDrawable(int drawableId) {
        arrowDrawableResId = drawableId;
        image.setPixelMap(drawableId);
    }

    private void setTextInternal(Object item) {
        if (selectedTextFormatter != null) {
            text.setText(selectedTextFormatter.format(item));
        } else {
            text.setText(item.toString());
        }
    }

    /**
     * Set the default spinner item using its index
     *
     * @param position the item's position
     */
    public void setSelectedIndex(int position) {
        if (adapter != null) {
            if (position >= 0 && position <= adapter.getCount()) {
                adapter.setSelectedIndex(position);
                selectedIndex = position;
                setTextInternal(adapter.getItemInDataset(position));
            } else {
                throw new IllegalArgumentException("Position must be lower than adapter count!");
            }
        }
    }



    /**
     * @deprecated use setOnSpinnerItemSelectedListener instead.
     */
    @Deprecated
    public void addOnItemClickListener(ListContainer.ItemClickedListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    /**
     * @deprecated use setOnSpinnerItemSelectedListener instead.
     */
    @Deprecated
    public void setOnItemSelectedListener(ListContainer.ItemSelectedListener onItemSelectedListener) {
        this.onItemSelectedListener = onItemSelectedListener;
    }

    public <T> void attachDataSource(List<T> list) {
        if (list == null) {
            return;
        }
        adapter = new NiceSpinnerAdapter<>(getContext(), list, textColor, backgroundSelector, spinnerTextFormatter, horizontalAlignment);
        setAdapterInternal(adapter);
    }

    public void setAdapter(BaseItemProvider adapter) {
        this.adapter = new NiceSpinnerAdapterWrapper(getContext(), adapter, textColor, backgroundSelector,
                spinnerTextFormatter, horizontalAlignment);
        setAdapterInternal(this.adapter);
    }

    public PopUpTextAlignment getPopUpTextAlignment() {
        return horizontalAlignment;
    }

    private <T> void setAdapterInternal(NiceSpinnerBaseAdapter<T> adapter) {
        if (adapter.getCount() >= 0) {
            // If the adapter needs to be set again, ensure to reset the selected index as well
            selectedIndex = 0;
            setTextInternal(adapter.getItemInDataset(selectedIndex));
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (isEnabled() && touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            if (adapter != null && adapter.getCount() > 0) {
                rotate2Up();
                showDropDown();
            } else {
                dismissDropDown();
            }
        }
        return true;
    }

    public void dismissDropDown() {
        rotate2Down();
        if (popupDialog != null) {
            popupDialog.destroy();
            popupDialog = null;
        }
    }

    // just used for junit test
    public ListContainer getContentView() {
        return listContainer;
    }

    // just used for junit test
    public Text getTextComponent() {
        return text;
    }

    // just used for junit test
    public Image getImageComponent() {
        return image;
    }

    private void showDropDown() {
        popupDialog = new PopupDialog(context, this);
        Component component = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_layout_popup_view, null, false);
        listContainer = (ListContainer) component.findComponentById(ResourceTable.Id_list_container);
        listContainer.setItemProvider(adapter);
        listContainer.setPaddingBottom(dropDownListPaddingBottom);
        popupDialog.setSize(getWidth(), ComponentContainer.LayoutConfig.MATCH_CONTENT);
        popupDialog.setAutoClosable(true);
        popupDialog.setCustomComponent(component);
        popupDialog.showOnCertainPosition(0, getLeft(), getBottom());
        listContainer.setItemClickedListener(new ListContainer.ItemClickedListener() {
            @Override
            public void onItemClicked(ListContainer listContainer, Component component, int position, long id) {
                // The selected item is not displayed within the list, so when the selected position is equal to
                // the one of the currently selected item it gets shifted to the next item.
                if (position >= selectedIndex && position < adapter.getCount()) {
                    position++;
                }
                selectedIndex = position;

                if (onSpinnerItemSelectedListener != null) {
                    onSpinnerItemSelectedListener.onItemSelected(NiceSpinner.this, component, position, id);
                }
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClicked(listContainer, component, position, id);
                }

                if (onItemSelectedListener != null) {
                    onItemSelectedListener.onItemSelected(listContainer, component, position, id);
                }
                adapter.setSelectedIndex(position);
                setTextInternal(adapter.getItemInDataset(position));
                dismissDropDown();
            }
        });
        listContainer.setItemLongClickedListener(new ListContainer.ItemLongClickedListener() {
            @Override
            public boolean onItemLongClicked(ListContainer listContainer, Component component, int position, long id) {
                // The selected item is not displayed within the list, so when the selected position is equal to
                // the one of the currently selected item it gets shifted to the next item.
                if (position >= selectedIndex && position < adapter.getCount()) {
                    position++;
                }
                selectedIndex = position;

                if (onSpinnerItemSelectedListener != null) {
                    onSpinnerItemSelectedListener.onItemSelected(NiceSpinner.this, component, position, id);
                }
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClicked(listContainer, component, position, id);
                }

                if (onItemSelectedListener != null) {
                    onItemSelectedListener.onItemSelected(listContainer, component, position, id);
                }
                adapter.setSelectedIndex(position);
                setTextInternal(adapter.getItemInDataset(position));
                dismissDropDown();
                return true;
            }
        });
        popupDialog.setDialogListener(new BaseDialog.DialogListener() {
            @Override
            public boolean isTouchOutside() {
                dismissDropDown();
                return false;
            }
        });
    }

    private void rotate2Up() {
        imageAnimatorProperty.setDuration(100);
        imageAnimatorProperty.setCurveType(Animator.CurveType.LINEAR);
        imageAnimatorProperty.rotate(180);
        imageAnimatorProperty.start();
        imageAnimatorProperty.release();
    }

    private void rotate2Down() {
        imageAnimatorProperty.setDuration(100);
        imageAnimatorProperty.setCurveType(Animator.CurveType.LINEAR);
        imageAnimatorProperty.rotate(0);
        imageAnimatorProperty.start();
        imageAnimatorProperty.release();
    }

    private int getPopUpHeight() {
        return Math.max(verticalSpaceBelow(), verticalSpaceAbove());
    }

    private int verticalSpaceAbove() {
        return getParentVerticalOffset();
    }

    private int verticalSpaceBelow() {
        return displayHeight - getParentVerticalOffset() - getEstimatedHeight();
    }

    public void hideArrow() {
        isArrowHidden = true;
        setArrowDrawableOrHide(arrowDrawable);
    }

    public void showArrow() {
        isArrowHidden = false;
        setArrowDrawableOrHide(arrowDrawable);
    }

    public boolean isArrowHidden() {
        return isArrowHidden;
    }

    public void setDropDownListPaddingBottom(int paddingBottomPx) {
        dropDownListPaddingBottom = paddingBottomPx;
    }

    public int getDropDownListPaddingBottom() {
        return dropDownListPaddingBottom;
    }

    public void setSpinnerTextFormatter(SpinnerTextFormatter spinnerTextFormatter) {
        this.spinnerTextFormatter = spinnerTextFormatter;
    }

    public void setSelectedTextFormatter(SpinnerTextFormatter textFormatter) {
        this.selectedTextFormatter = textFormatter;
    }


    public void performItemClick(int position, boolean showDropdown) {
        if (showDropdown) showDropDown();
        setSelectedIndex(position);
    }

    /**
     * only applicable when popup is shown .
     *
     * @param view
     * @param position
     * @param id
     */
    public void performItemClick(Component view, int position, int id) {
        showDropDown();
        if (listContainer != null) {
            listContainer.executeItemClick(view, position, id);
        }
    }

    public OnSpinnerItemSelectedListener getOnSpinnerItemSelectedListener() {
        return onSpinnerItemSelectedListener;
    }

    public void setOnSpinnerItemSelectedListener(OnSpinnerItemSelectedListener onSpinnerItemSelectedListener) {
        this.onSpinnerItemSelectedListener = onSpinnerItemSelectedListener;
    }

    public void setPopupTextAliment(PopUpTextAlignment popUpTextAlignment) {
        horizontalAlignment = popUpTextAlignment;
    }

    public void setStrArrayData(int strArrayNameId) {
        try {
            String[] entries = getResourceManager().getElement(strArrayNameId).getStringArray();
            if (entries != null) {
                attachDataSource(Arrays.asList(entries));
            }
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
    }
}

